#include "repo_item.h"
#include "messiah/resource_material.h"
#include "repository.h"
#include "debug/logger.h"
#include "fileio/fileio.h"
#include "core/cbuffer.h"
#include "repo_res.h"
#include "repo_res_material.h"
#include "repo_res_texture.h"
#include "messiah/resource/repo/repo_loader.h"
#include "misc/strutil.h"

namespace renderit
{
	namespace messiah
	{
		repo_item::repo_item(repo_loader * loader)
			: repo_loader_(loader)
			, repo_fpath_(loader->repo_dir())
			, repo_name_(loader->repo_name())
			, fsize_(0)
			, repo_res_(nullptr)
		{

		}

		repo_item::~repo_item()
		{
			if (repo_res_)
			{
				delete repo_res_;
			}
		}

		void repo_item::deserialize(tinyxml2::XMLElement* elem, bool exist)
		{
			root_elem_ = elem;
			type_ = GResourceTypeFromString(elem->GetChildText("Type", ""));	// _fetch_text(elem, "Type"));
			guid_ = elem->GetChildText("GUID", "");								//_fetch_text(elem, "GUID");
			package_ = elem->GetChildText("Package", "");						// _fetch_text(elem, "Package");
			class_ = elem->GetChildText("Class", "");							// _fetch_text(elem, "Class");
			name_ = elem->GetChildText("Name", "");								// _fetch_text(elem, "Name");

			// Deps
			xml_deps_ = const_cast<tinyxml2::XMLElement*>(elem)->ChildrenElements("Deps");
			for (auto& d : xml_deps_)
			{
				deps_.push_back(d->GetText());
			}

			// Annotations
			const tinyxml2::XMLElement * annotation = elem->FirstChildElement("Annotation");
			if (annotation)
			{
				std::vector<const tinyxml2::XMLElement*> annos = annotation->ChildrenElements("Anno");
				for (auto& anno : annos)
				{
					//anno_.annotations[_fetch_text(anno, "Key")] = _fetch_text(anno, "Value");
					std::string val = anno->GetChildText("Value", "");
					anno_.annotations[anno->GetChildText("Key", "")] = val;
					if (strutil::str_is_guid(val))
					{
						anno_deps_.push_back(std::make_pair(val, const_cast<tinyxml2::XMLElement*>(anno->FirstChildElement("Value"))));
					}
				}
			}

			// Scan Resource Object Files
			scan_res_obj_files(exist);
			
			// FileSize
			fsize_ = fileio::get_size(repo_res_dir_);
		}

		void repo_item::scan_res_obj_files(bool exist)
		{
			const std::string& sub_dir = GResourceDirFromType(type_);
			if (!sub_dir.size())
			{
				return;
			}
			
			core::cbuffer cb;
			cb.add(repo_fpath_.c_str(), repo_fpath_.length());
			if (cb.tail() != '/')
			{
				cb.add('/');
			}
			cb.add(sub_dir.c_str(), sub_dir.length());
			cb.add('/');
			cb.add(guid_.c_str(), 2);
			cb.add('/');
			cb.add('{');
			cb.add(guid_.c_str(), guid_.length());
			cb.add('}');
			cb.add(0);
			repo_res_dir_ = cb.buf();

			if (exist && !fileio::exists(repo_res_dir_))
			{
				logger::warn("RepoItem(%s) in (%s), Class(%s) Missing ResourceObject Files(%s)!", guid_.c_str(), repo_fpath_.c_str(), class_.c_str(), repo_res_dir_.c_str());
				return;
			}

			switch (type_)
			{
			case EResourceType_Material:
				repo_res_ = new repo_res_material(this, repo_res_dir_);
				break;

			case EResourceType_Texture:
				repo_res_ = new repo_res_texture(this, repo_res_dir_);
				break;

			default:
				repo_res_ = new repo_res(this, repo_res_dir_);
				break;
			}

			repo_res_->load();
			
			fsize_ = repo_res_->get_fsize();
		}

		const std::string& repo_item::get_res_md5()
		{
			if (repo_res_md5_.size())
			{
				return repo_res_md5_;
			}
			if (repo_res_)
			{
				repo_res_md5_ = repo_res_->get_md5();
			}
			return repo_res_md5_;
		}

		std::vector<std::string> repo_item::get_deps() const
		{
			std::vector<std::string> r;
			r.insert(r.end(), deps_.begin(), deps_.end());
			for (auto& p : anno_deps_)
			{
				r.push_back(p.first);
			}
			if (repo_res_)
			{
				std::vector<std::string> tmp = repo_res_->get_deps();
				r.insert(r.end(), tmp.begin(), tmp.end());
			}
			return r;
		}

		uint32 repo_item::replace_guid(const std::string& from_guid, const std::string& to_guid)
		{
			if (!root_elem_)
			{
				return 0;
			}

			uint32 count = 0;
			// 1. replace in Deps
			for (uint32 i = 0; i < deps_.size(); ++i)
			{
				if (deps_[i] == from_guid)
				{
					xml_deps_[i]->SetText(to_guid.c_str());
					count++;
					repo_loader_->set_dirty();
				}
			}

			// 2. replace in Anno Deps
			for (uint32 i = 0; i < anno_deps_.size(); ++i)
			{
				if (anno_deps_[i].first == from_guid)
				{
					anno_deps_[i].second->SetText(to_guid.c_str());
					count++;
					repo_loader_->set_dirty();
				}
			}

			// 2. replace in res
			if (repo_res_)
			{
				count += repo_res_->replace_guid(from_guid, to_guid);
				repo_res_->save();
			}

			return count;
		}

		bool repo_item::remove()
		{
			if (!root_elem_)
			{
				return false;
			}

			root_elem_->Parent()->DeleteChild(root_elem_);
			root_elem_ = nullptr;
			repo_loader_->set_dirty();
			return fileio::remove_file(repo_res_dir_);
		}
	}
}